import java.util.Random;

 /**
 * @author peterstone
 *The optimization of neural network by genetic algorithm
 */
public class GeneticAlgorithm {
	
	Network[] population;
	double[] errors;
	int geneticNumber;
	Data trainData;
	double[][] input;
	int trainingNumber;
	double errorThreshold;
	
	public static int GMAX = 10;
	
	public GeneticAlgorithm(int inputLayerNumber, int hiddenLayerNumber, int outputLayerNumber, double learningrate, Data data)
	{

		trainData = data;
		trainingNumber = (int) (trainData.dataset.size() * 0.1);  //the number of the training set for GA
		geneticNumber = (int) (trainingNumber * 0.1); //the number of genome
		
		
		population = new Network[geneticNumber]; //the population constructed by neural network
		Random seed = new Random();
		input = new double[trainingNumber][trainData.attributeNum];
		
		for (int i = 0; i < trainingNumber; i++){
			int index = Math.abs(seed.nextInt()) % trainData.dataset.size(); //Sample from the training set.
			
			for (int j = 0; j < trainData.attributeNum; j++){
				input[i][j] = trainData.dataset.get(index)[j];
			}
		}
		
		for(int i = 0; i < geneticNumber; i++)
		{
			population[i] = new Network(inputLayerNumber, hiddenLayerNumber, outputLayerNumber);
			if (outputLayerNumber > 2){
				population[i].setClassNumber(outputLayerNumber);
			}
			population[i].setLearningRate(learningrate);
		}
		
		errors =  new double[geneticNumber];
		errorThreshold = 0.15;
	}
	
	public Network run()
	{
		float error = 0;
		
		for(int i = 0; i < GMAX; i++)
		{
			for(int j = 0; j < geneticNumber; j++)
			{
				for(int k = 0; k < trainingNumber; k++)
				{
					if(!population[j].testTuple(input[k]))
						error++;
				}
				
				if(error/ trainingNumber < errorThreshold)    //the evaluation function 
				{
					return population[j];
				}			
				error = error / trainingNumber;
				errors[j] = error;
				error = 0;
			}
			sortByError();
			newPopulation();
		}
		
		return  population[0];
	}
	
	public void sortByError() //sort the genome in the population by the errors between the output and the label
	{
		int n = geneticNumber;
		int disorder = n;
		
	 	while (disorder > 0) {
			disorder = 0;
			for(int i=1;i < n;i++) {
				if (errors[i] < errors[i-1]) {
					double temp = errors[i-1];
					errors[i-1] = errors[i];
					errors[i] = temp;

					Network tempnet = population[i-1];
					population[i-1] = population[i];
					population[i] = tempnet;

					disorder++;
				}
			}
			n--;
		}
	 	for(int i = 0; i < n; i++)
	 	{
	 		if(i == 0)
	 		System.out.println(errors[i]);
	 	}
	 	System.out.println("______________");
	}
	
	public void newPopulation() // reproduce the genome
	{
		int id1, id2;
		
		Random seed = new Random();
		
		for (int i=0;i < geneticNumber /2;i++) {
			id1 = seed.nextInt(geneticNumber /2);
			id2 = seed.nextInt(geneticNumber /2) + geneticNumber /2;
			
			int aNum = seed.nextInt(4); //compute the random number for the hybridization
			
			if(aNum == 0)
			{
				for(int j = 0; j < population[0].hiddenLayerNumber; j++)
				{
					population[id2].hiddenBias[j] = population[id1].hiddenBias[j];
				}
			}
			else if(aNum == 1)
			{			
				for(int j = 0; j < population[0].outputLayerNumber; j++)
				{
					population[id2].hiddenBias[j] = population[id1].outputBias[j];
				}
			}
			else if(aNum == 2)
			{	
				for(int j = 0; j < population[0].inputLayerNumber; j++)
				{
					for(int k = 0; k < population[0].hiddenLayerNumber; k++)
					population[id2].inputWeight[j][k] = population[id1].inputWeight[j][k];
				}
			}
			else
			{	
				for(int j = 0; j <  population[0].hiddenLayerNumber; j++)
				{
					for(int k = 0; k < population[0].outputLayerNumber; k++)
					population[id2].outputWeight[j][k] = population[id1].outputWeight[j][k];
				}
			}

			if (seed.nextInt(10) < 2 && id1 != 0) //heteromorphosis
			{
				population[id1].initNetwork();
				population[id2].initNetwork();
			}

		}
	}
}
